package hyl.base.net.iot;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import hyl.core.conf.MyConst;
import hyl.core.data.Bytes;
import hyl.core.data.ExMap;

import hyl.core.net.IReader;
import hyl.core.net.NioClient;
import hyl.core.net.NioServer;

import hyl.core.run.IDoEachT1;
import hyl.core.run.MyTimer;

/*
注册末端设备D 

:param imei:
:param 设备编号:  不要用中文 只能用字母和数字
:param conn:
:return:
*/
public class ABsrv {
	int Bid;
	String Bname;
	NioClient cliA = null;// cliA 可以不用
	NioServer srvB;
	ExMap<Integer, InfoMap> C直连字典; // 只保存最临近的节点
	// ExMap<byte[], InfoMulti> B级联字典; // 保存非临近的下级节点
	ExMap<IReader, Integer> C连接池;
	/**
	 * 级联字典 key=paths boolean
	 * 
	 * B级联字典 用于 保存 B节点的状态,判断他们是否存活
	 */
	ExMap<Bytes, Integer> B级联字典;
	/**
	 * D字典 用于快速定位D,通常用于顶层
	 */
	ExMap<Integer, int[]> D末端字典;// key=did value paths
	MyTimer ping = null;

	public MyConst const1;
	public Charset HTTP编码;
	public Charset BA编码;

	// HEART_C 必须小于TIMEOUT_C
	public int HEART_C_MS = 60000; // 60s 心跳间隔时间
	public int TIMEOUT_C_MS = 120000; // 120s iC端连接超时(毫秒)
	public int TIMEOUT_C_S = 120; // 120s iC端连接超时(毫秒)
	public InetSocketAddress inetSocketA = null;// 上级服务的ip地址
	public InetSocketAddress inetSocketB = null;// 当前服务的ip地址

	/**
	 * 配置
	 */
	public ABsrv() {
		Bname = Conf.A_Servname;
		TIMEOUT_C_MS = Conf.TIMEOUT_C_MS;
		inetSocketA = Conf.inetSocketA;
		inetSocketB = Conf.inetSocketB;
		ini();
	}

	public ABsrv(String 服务器名, int 超时毫秒, InetSocketAddress 上级服务器地址, InetSocketAddress 本机服务地址) {
		Bname = 服务器名;
		TIMEOUT_C_MS = 超时毫秒;
		inetSocketA = 上级服务器地址;
		inetSocketB = 本机服务地址;
		ini();

	}

	public void ini() {
		BA编码 = Conf.BA编码;
		TIMEOUT_C_S = TIMEOUT_C_MS / 1000;
		HEART_C_MS = TIMEOUT_C_MS / 2;
		Bid = PFun.dname2Int(Bname);
		C直连字典 = new ExMap<>(TIMEOUT_C_MS);
		C连接池 = new ExMap<>(TIMEOUT_C_MS);
		B级联字典 = new ExMap<>(TIMEOUT_C_MS);
		D末端字典 = new ExMap<>(TIMEOUT_C_MS);
		C直连字典.set过期函数((过期键, info) -> {
			// 过期了,发送广播给上级
		});
		C连接池.set过期函数((reader, 过期键) -> {
			// 过期了,删除 该 socket
			reader.close();
		});

		srvB = new NioServer(inetSocketB);
		srvB.setReaderHandler(() -> {
			return new IDoEachT1<IReader, byte[]>() {
				public boolean isOver(IReader skt, byte[] b数据) {
					return receiveBC(skt, b数据);
				};
			};
		});
		if (inetSocketA != null) {
			cliA = new NioClient(inetSocketA);
			cliA.setHandler((read, data) -> {
				return receiveAB(read, data);
			});
			ping = MyTimer.getInstance();
		}
	}

	public IDoEachT1<IReader, byte[]> h上行前置器;
	public IDoEachT1<IReader, byte[]> h上行后置器;
	public IDoEachT1<IReader, byte[]> h上行内部指令;
	public IDoEachT1<IReader, byte[]> h下行前置器;
	public IDoEachT1<IReader, byte[]> h下行后置器;

	boolean do上行后置器(IReader reader, byte[] bte) {
		if (h上行后置器 != null) {
			return h上行后置器.isOver(reader, bte);
		}
		return false;
	}

	boolean do下行后置器(IReader reader, byte[] bte) {
		if (h下行后置器 != null) {
			return h下行后置器.isOver(reader, bte);
		}
		return false;
	}

	boolean do内部指令处理(byte 指令1, IReader reader, byte[] bte) {
		if (h上行内部指令 != null) {
			return h上行内部指令.isOver(reader, bte);
		}
		return false;
	}

//############################ B服务器 接受 C的接入##########################
	/**
	 * 注册接入终端C
	 * 
	 * @param 终端身份id
	 * @param 终端设备id
	 * @param 终端连接
	 */

	public void regBC(int id, IReader 终端C连接) {
		InfoMap cinfo = new InfoMap(id, 终端C连接);
		System.out.println(""+Bid+".regBC(" + cinfo.id + ")" + 终端C连接.getRemoteAddress());
		C直连字典.put(cinfo.id, cinfo);
		C连接池.put(终端C连接, cinfo.id);
		int[] path = new int[] { id, Bid };
		sendtoAB(path, Netpg3.CMD_B注册);

	}

	/**
	 * 收到C的反馈 可能有三种情况 第一次接入 ping入 应答
	 */
	boolean receiveBC(IReader reader, byte[] bte) {
		if (h上行前置器 != null) {
			boolean flag = h上行前置器.isOver(reader, bte);
			if (flag)
				return true;
		}
		Netpg3 npNetpg3 = new Netpg3();
		byte[] sy = npNetpg3.deserial(bte);

		// 如果该终端没有注册过,先注册
		if (!C连接池.containsKey(reader)) {
			//System.out.println("receiveBC():" + JSON.toJSONString(npNetpg3.ii路径));
			if (npNetpg3.b指令1 == Netpg3.CMD_注册 && npNetpg3.existPath()) {
				regBC(npNetpg3.ii路径[0], reader);
			}
			return do内部指令处理(Netpg3.CMD_注册, reader, bte);
		}
		int cid = C连接池.get(reader);
		InfoMap infob = C直连字典.get(cid);

		switch (npNetpg3.b指令1) {
		case Netpg3.CMD_心跳:
			int[] path = new int[] { cid, Bid };
			sendtoAB(path, Netpg3.CMD_B心跳);
			// C直连字典.reStamp(cid);
			// 因为get() 中就包含了 可以省略
			return do内部指令处理(Netpg3.CMD_心跳, reader, bte);
		case Netpg3.CMD_B注册:
			B级联字典.put(npNetpg3.getPaths(), npNetpg3.ii路径[0]);
			npNetpg3.ii路径 = PFun.addPath(npNetpg3.ii路径, Bid);
			sendtoAB(npNetpg3);
			return do内部指令处理(Netpg3.CMD_B注册, reader, bte);
		case Netpg3.CMD_B心跳:
			B级联字典.reStamp(npNetpg3.getPaths());
			npNetpg3.ii路径 = PFun.addPath(npNetpg3.ii路径, Bid);
			sendtoAB(npNetpg3);
			return do内部指令处理(Netpg3.CMD_B心跳, reader, bte);

		case Netpg3.CMD_D注册:
			int D末端 = PFun.getPath接收方(npNetpg3.ii路径);
			int[] value = PFun.addPath(npNetpg3.ii路径, Bid);
			//  System.out.println(D末端+JSON.toJSONString(value));
			D末端字典.put(D末端, value);
			npNetpg3.ii路径 = value;
			sendtoAB(npNetpg3);
			return do内部指令处理(Netpg3.CMD_D注册, reader, bte);
		case Netpg3.CMD_D心跳:
			npNetpg3.ii路径 = PFun.addPath(npNetpg3.ii路径, Bid);
			B级联字典.reStamp(npNetpg3.getPaths());
			sendtoAB(npNetpg3);
			return do内部指令处理(Netpg3.CMD_D心跳, reader, bte);
		}
		Bytes key = npNetpg3.getKey();
		// 以上部分 是从下往上直接发送的指令
		if (infob.isBlock(key))
			infob.setReply(key, npNetpg3);
		else {
			// 否则 是 没有等待情况下的反馈,属于主动反馈,这种特殊情况情况,交给最近一次的发送端处理
			sendtoAB(npNetpg3);
		}
		if (sy.length > 0)// 如果粘包了,递归执行
			receiveBC(reader, sy);
		return do上行后置器(reader, bte);
	}

	protected void removeBC(IReader reader, int cid) {
		C直连字典.remove(cid);
		C连接池.remove(reader);
		// My.rds_C.del(MyFun.str2Bytes(cid, My.BC编码));
		// log.info("断开客户端 ()",身份证id)
	}

	/**
	 * Netpg3 npNetpg3 = new Netpg3(); npNetpg3.set(path, My.BC编码, cmd, null);
	 * 
	 * @param npNetpg3
	 */
	public void sendToBC(Netpg3 npNetpg3) {
		int cid = PFun.getPath下层节点(npNetpg3.ii路径, Bid);
		// System.out.println(JSON.toJSONString(C直连字典));
		InfoMap info = C直连字典.get(cid);// 每个子节点还有有多个子节点
		//System.out.println("--1--"+cid);
		if (info == null) {
			//System.out.println("-sendToBC-2--"+Bid+"  "+cid);
			return;
		}	
		IReader readerBC = info.reader;
		try {
			Bytes key=npNetpg3.getKey();
			info.openReply(key);
			readerBC.send(npNetpg3.serial());
			//System.out.println("--3--");
			Netpg3 结果 = info.getReply(key);// 如果没有应答转为null字符串
			sendtoAB(结果);// 返回结果给上级节点
			//System.out.println("--4--");
		} catch (Exception e) {
			//System.out.println("-sendToBC-0--");
			e.printStackTrace();
			removeBC(readerBC, cid);
		}
	}

	// ############################ 连接 A服务器 ##########################
	// 转交给A 前提路径中有A存在
	public void sendtoAB(Netpg3 pg3) {
		if (cliA == null|| pg3==null)
			return;
		try {
			//if (PFun.getPath前(pg3.ii路径, Bid) > -1)
			cliA.send(pg3.serial());
		} catch (IOException e) {
		}
	}

	// 发送给A
	public void sendtoAB(int[] path, byte cmd) {
		Netpg3 npNetpg3 = new Netpg3();
		npNetpg3.set(path, cmd);
		sendtoAB(npNetpg3);
	}

	/**
	 * 收到A的消息并处理 返回发送是否成功的状态
	 */
	boolean receiveAB(IReader reader, byte[] data) {
		if (h下行前置器 != null) {
			boolean flag = h下行前置器.isOver(reader, data);
			if (flag)
				return true;
		}
		Netpg3 npNetpg3 = new Netpg3();
		npNetpg3.deserial(data);
		// 如果有路径,并且最后目的地不是自己,继续向下转发
		if (npNetpg3.existPath()) {
			if (npNetpg3.ii路径[0] == Bid) {
				// A应答B
				return false;
			} else {
				// 通过B节点转发消息到B的下级节点C
				sendToBC(npNetpg3);// # 转发送指令给设备C
				return do下行后置器(reader, data);
			}
		}
		return false;
	}

	/**
	 * 连接A等于向A注册B
	 */
	public void connAB() {
		sendtoAB(new int[] { Bid }, Netpg3.CMD_注册);
	}

	/**
	 * pingAB测试
	 */
	public void pingAB() {
		if (ping != null) {
			ping.start(() -> {
				try {
					sendtoAB(new int[] { Bid }, Netpg3.CMD_B心跳);
				} catch (Exception e) {
					ta.interrupt();
					cliA.close();
				}

			}, HEART_C_MS);
		}
	}

	Thread ta, tb;

	// 入口程序
	/**
	 * 启动 ab的客户端
	 * 
	 * 启动 bc的服务端
	 */
	public void start() {
		if (tb == null) {
			// 开启B服务,等待接收C消息,处理C发送的消息
			tb = srvB.start();
		}
		if (cliA == null)
			return;
		// 连接A服务,接收A的反馈
		if (ta == null) {
			ta = cliA.start();
		}
		connAB();
	}

	public static void main(String[] args) {
		new ABsrv().start();
	}

}
